#pragma once
#include <iostream>
#include <cstdio>
#include <string>
#include <unistd.h>
#include <fstream>
#include <memory>
#include <filesystem> // C++17, 需要⾼版本编译器和-std=c++17 
#include <time.h>
#include <sstream>
#include "Lock.hpp"

using namespace LockModule;

// 获取当前系统的时间
std::string xtsj()
{
    time_t time_s = ::time(nullptr);//获取时间戳
    struct tm result;
    localtime_r(&time_s, &result);//把时间戳转换为时间信息
    char buff[1024];//数组
    //整理好写到数组
    snprintf(buff, sizeof(buff), "%4d-%02d-%02d  %02d:%02d:%02d",
             result.tm_year + 1900, 
             result.tm_mon + 1,    
             result.tm_mday,
             result.tm_hour,
             result.tm_min,
             result.tm_sec);
    return buff;
}

// 1.构建日志字符串  2.刷新落盘(显示器,文件)  
// 1.日志文件的默认路径和文件名
const std::string  log = "./log/";
const std::string name = "log.txt";
// 2.日志等级
enum Logeve
{
    DEBUG = 1,//
    INFO,    //正常信息
    WAR,     //出错不影响代码
    ERROR,   //错误
    FATAL    //致命错误整个程序停掉
};
// 将对应的日志等级转换为字符串，方便看
std::string levestring(Logeve leve)
{
    switch (leve)
    {
    case Logeve::DEBUG:
        return "DEBUG";
    case Logeve::INFO:
        return "INFO";
    case Logeve::WAR:
        return "WAR";
    case Logeve::ERROR:
        return "ERROR";
    case Logeve::FATAL:
        return "FATAL";
    default:
        return "NULL";
    }
}

// 3.刷新策略
class logstrat
{
public:
    virtual ~logstrat() = default;                     // 策略的构造函数
    virtual void synlog(const std::string& mess) = 0;  // 不同模式核⼼是刷新⽅式的不同
};

// 3.1控制台策略
class console : public logstrat
{
public:
    console() {}
    ~console() {}
    void synlog(const std::string &mess)
    {
        LockGuard lock(_lock);//加锁-析构解锁---防止多线程同时写数据
        std::cout << mess << std::endl;//打印到显示器
    }
private:
    Mutex _lock;
};

// 3.2文件级(磁盘)策略
class fileLog : public logstrat
{
public:
    //日志路径和名称
    fileLog(const std::string &log = log, const std::string &logname = name)
        : _log(log),
          _logname(logname)
    {
        LockGuard lock(_lock);//锁
        //确认_log是存在的
        if (std::filesystem::exists(_log))
        {
            //文件存在直接返回
            return;
        }
        try//使用程序可能路径不对-或权限文件创建不成功--需要捕捉异常
        {
            std::filesystem::create_directories(_log);//不存在新建文件
        }
        catch (std::filesystem::filesystem_error &e)
        {
            std::cerr << e.what() << "\n";
        }
    }
    ~fileLog() {}
    //缓冲区刷新到文件
    void synlog(const std::string &mess)
    {
        LockGuard lock(_lock);//防止多线程同时刷新

        std::string log = _log + _logname;//  ./log/log.txt
        //流-日志写入，一定是追加的--C++写法和C语言的FILE一样
        std::ofstream out(log, std::ios::app);//std::ios::app不存在新建，打开了追加写入
        if (!out.is_open())
        {
            return;
        }
        //成功打开
        //往文件里，写mess
        out << mess << "\n";

        out.close();
    }
private:
    std::string _log;
    std::string _logname;

    Mutex _lock;//锁
};
//日志类--构建日志字符串，根据策略，进行刷新
class logger
{
public:
    logger()
    {
        //默认采用console往显示器打印
        _strat = std::make_shared<console>();
    }
    //下面2个刷新策略，想用那个用那个
    //打开控制台刷新日志
    void ENAconsoleLog()
    {
        _strat = std::make_shared<console>();
    }
    //文件级(磁盘)刷新策略
    void EANfile()
    {
        _strat = std::make_shared<fileLog>();
    }
    ~logger() {}
    // 一条完整的信息: [2024-08-04 12:27:03] [DEBUG] [202938] [main.cc] [16] + 日志的可变部分(<< "hello world" << 3.14 << a << b;)
    class logmess
    {
    public:
        logmess(Logeve level,const std::string &filename, int line, logger& loger)
            : _curr(xtsj()),
              _level(level),
              _pid(getpid()),
              _filename(filename),
              _line(line),
              _loger(loger)
        {
            //字符串流-
            std::stringstream ssbuff;
            ssbuff << "[" << _curr << "] "
                   << "[" << levestring(_level) << "] "
                   << "[" << _pid << "] "
                   << "[" << _filename << "] "
                   << "[" << _line << "] - ";
            //将上面整个字符串流转换成字符串放到_loginfo
            _loginfo = ssbuff.str();
        }
        template<class T>
        logmess& operator<<(const T& info)
        {
            //将info放到字符串流中
            std::stringstream ss;
            ss << info;
            //拼接到日志记录的后面
            _loginfo += ss.str();
            //后面可能是这样 << a << "ds" <<i所以使用*this返回
            return *this;
        }
        ~logmess()
        {
            if (_loger._strat)
            {
                // 根据指定策略刷新一条方法
                _loger._strat->synlog(_loginfo);
            }
        }
    private:
        std::string _curr;                  //当前日志时间
        Logeve _level;                      //日志等级
        pid_t _pid;                         //进程pid
        std::string _filename;              //原文件名称
        uint32_t _line;                     //日志所在的行号
        logger& _loger;                     //负责根据不同的策略进行刷新
        std::string _loginfo;               //一条完整的日志记录
    };

    //重载()因为想这样传LOG(DEBUG）
    //这里没有写引用，就是故意拷贝
    logmess operator()(Logeve level,const std::string &filename, int line)
    {
        return logmess(level, filename, line, *this);
    }

private:
    //智能指针
    std::shared_ptr<logstrat> _strat; //日志刷新的策略方案
};

logger logg;

// 宏替换--是()会调用上面的operator重载()
#define LOG(level) logg(level, __FILE__, __LINE__)
#define ENA_CONSO_LELOG() logg.ENAconsoleLog() //显示器
#define EANfile() logg.EANfile();              //文件日志